﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
using System.IO;
using OpenNETCF.IO.Ports;


namespace robotGUI
{
    public partial class serialControl : UserControl
    {
        public SerialPort Port = null;
        Ports availablePorts;
        
        /// <summary>
        /// Serial Port is open.
        /// </summary>
        public bool IsOpen
        {
            get
            {
                if (Port != null && Port.IsOpen) return true;
                else return false;
            }
        }

        public serialControl()
        {
            InitializeComponent();
            availablePorts = new Ports();   //Create RS232 port interface
            availablePorts.ScanDevices(portScan_RunCompleted);
            cbxBaud.Text = Properties.Settings.Default.baudRate;
            Font = new Font("Microsoft Sans Serif", 8.25f); //prevent switch to Segoe UI
        }

        //Serial port description tooltip
        private void cbxPort_DrawItem(object sender, DrawItemEventArgs e)
        {
            if (e.Index < 0) { return; } // added this line thanks to Andrew's comment
            cbxPortEntry item = (cbxPortEntry)cbxPort.Items[e.Index];
            e.DrawBackground();
            using (SolidBrush br = new SolidBrush(e.ForeColor))
            { e.Graphics.DrawString(item.Name, e.Font, br, e.Bounds); }
            if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
            { toolTip1.Show(item.Description, cbxPort, e.Bounds.Right, e.Bounds.Bottom); }
            e.DrawFocusRectangle();
        }

        //Re-scan ports when the Ports dropdown is opened
        private void cbxPort_DropDown(object sender, EventArgs e)
        {
            cbxPort.Enabled = false;
            cbxPort.Items.Clear();
            cbxPort.Text = "Scanning..";
            availablePorts.ScanDevices(portScan_RunCompleted);
        }

        private void cbxPort_DropDownClosed(object sender, EventArgs e)
        {
            toolTip1.Hide(cbxPort);
        }

        //Callback from serial port scan
        private void portScan_RunCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            cbxPort.Enabled = true;
            cbxPort.Items.AddRange(((List<cbxPortEntry>)e.Result).ToArray());
            //No selection? Choose something reasonable
            if (Properties.Settings.Default.comPort == null || Properties.Settings.Default.comPort == "")
            {
                //Use first available com port
                foreach (cbxPortEntry port in cbxPort.Items)
                    if (port.Name.Contains("COM"))
                    {
                        cbxPort.SelectedItem = port;
                        break;
                    }
            }
            //Restore previous selection
            else cbxPort.Text = Properties.Settings.Default.comPort;
        }

        //Port opening event
        public delegate void PortOpeningHandler(object sender, EventArgs e);
        public event PortOpeningHandler PortOpening;
        public void OnPortOpening()
        {
            if (PortOpening != null)
            {
                PortOpening(this, EventArgs.Empty);
            }
        }

        //Port opened event
        public delegate void PortOpenedHandler(object sender, EventArgs e);
        public event PortOpenedHandler PortOpened;
        public void OnPortOpened()
        {
            if (PortOpened != null)
            {
                PortOpened(this, EventArgs.Empty);
            }
        }

        //Port closed event
        public delegate void PortClosedHandler(object sender, EventArgs e);
        public event PortClosedHandler PortClosed;
        public void OnPortClosed()
        {
            if (PortClosed != null)
            {
                PortClosed(this, EventArgs.Empty);
            }
        }


        //Data received event
        public delegate void DataReceivedEventHandler(object sender, SerialReceivedEventArgs e);
        public event DataReceivedEventHandler DataReceived;
        public void OnDataReceived(object sender, SerialReceivedEventArgs e)
        {
            if (DataReceived != null)
            {
                DataReceived(this, e);
            }
        }

        //Port error event
        public delegate void PortErrorEventHandler(object sender, PortErrorEventArgs e);
        public event PortErrorEventHandler PortError;
        public void OnPortError(string Message)
        {
            if (PortError != null)
            {
                PortError(this, new PortErrorEventArgs(Message));
            }
        }


        //Open/Close serial port
        private void chkOpen_CheckedChanged(object sender, EventArgs e)
        {
            if (Port != null)
            {
                Thread CloseDown = new Thread(new ThreadStart(ForceClose)); //close port in new thread to avoid hang
                CloseDown.Start(); //close port in new thread to avoid hang
                chkOpen.Text = "Connect";
                chkOpen.Image = Properties.Resources.disconnect;
                picHb.Image = Properties.Resources.heart_grey;
                comTimer.Enabled = false;
                OnPortClosed();
            }
            if (chkOpen.Checked)
            {
                int baud = 0;
                int.TryParse(cbxBaud.Text, out baud);
                try
                {
                    string winName = @"\\.\" + cbxPort.Text.Trim(':');
                    Port = new SerialPort(winName, baud);
                    chkOpen.Text = "Connecting..";
                    OnPortOpening();
                    chkOpen.Image = Properties.Resources.connect;
                    Application.DoEvents();
                    Port.Open();
                    Port.ErrorEvent += new SerialErrorEventHandler(Port_ErrorEvent);
                    Port.ReceivedEvent += new SerialReceivedEventHandler(OnDataReceived);
                    chkOpen.Text = "Disconnect";
                    comTimer.Enabled = true;
                    OnPortOpened();
                }
                catch (Exception ex)
                {
                    chkOpen.Checked = false;
                    OnPortError("Serial Port: " + ex.Message);
                    chkOpen.Text = "Connect";
                    chkOpen.Image = Properties.Resources.disconnect;
                    picHb.Image = Properties.Resources.heart_grey;
                    comTimer.Enabled = false;
                }
            }
        }

        //public serial port Close() method
        /// <summary>
        /// Close serial port.
        /// </summary>
        public void Close()
        {
            if (chkOpen.Checked) chkOpen.Checked = false;
        }

        //Port error handler
        void Port_ErrorEvent(object sender, SerialErrorEventArgs e)
        {
            OnPortError(e.Description);
        }

        //Manually close serial port
        /// <summary>
        /// Force serial port to close in current thread.
        /// </summary>
        public void ForceClose()
        {
            try
            {
                if (Port.IsOpen)
                {
                    Port.DiscardInBuffer();
                    Port.Close(); //close the serial port
                }
                Port = null;
            }
            catch (Exception ex)
            {
                OnPortError(ex.Message); //catch any serial port closing error messages
            }
        }

        private void comTimer_Tick(object sender, EventArgs e)
        {
            //Check if port closed unexpectedly
            if (Port != null && !Port.IsOpen)
            {
                chkOpen.Checked = false;
                chkOpen.Text = "Open";
                picHb.Image = Properties.Resources.heart_grey;
                comTimer.Enabled = false;
                OnPortClosed();
            }
        }

        //Toggle heartbeat picture
        bool hbPic = false;
        /// <summary>
        /// Toggle heart image grey <-> red
        /// </summary>
        public void HeartBeat()
        {
            if (hbPic = !hbPic) picHb.Image = Properties.Resources.heart;
            else picHb.Image = Properties.Resources.heart_grey;
        }

        private void cbxPort_TextChanged(object sender, EventArgs e)
        {
            if (cbxPort.Text != "Scanning..")
                Properties.Settings.Default.comPort = cbxPort.Text;
        }

        private void cbxBaud_TextChanged(object sender, EventArgs e)
        {
            Properties.Settings.Default.baudRate = cbxBaud.Text;
        }

        //-----------------------------------------------------------------------
        //Low-level serial communication

        //IntFloat extracts bytes from single precision float
        [StructLayout(LayoutKind.Explicit)]
        struct IntFloat
        {
            [FieldOffset(0)]
            public float FloatValue;

            [FieldOffset(0)]
            public uint IntValue;
        }

        /// <summary>
        /// Write 'count' bytes from byte buffer to serial port
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="count"></param>
        public void Write(byte[] buffer, int count)
        {
            try { if (Port != null && Port.IsOpen) Port.Write(buffer, 0, count); }
            catch (IOException) { } //Ignore leaky pipes
        }

        /// <summary>
        /// Send (Command = byte[4] Values) to the serial port
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="values"></param>
        public void sendValue(Command cmd, byte[] values)
        {
            byte[] msg = new byte[6];
            //Send command and value
            msg[0] = (byte)cmd;
            msg[1] = (byte)':';
            values.CopyTo(msg, 2);
            Write(msg, 6);
        }

        /// <summary>
        /// Send (Command = (float)Value) to the serial port
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="values"></param>
        public void sendValue(Command cmd, float value)
        {
            //Converts the float value to raw bytes
            IntFloat infl = new IntFloat();
            infl.FloatValue = value;
            uint raw = infl.IntValue;

            //Sends the float value as four bytes
            sendValue(cmd, new byte[] { (byte)(raw >> 24),
                (byte)(raw >> 16 & 0xff),
                (byte)(raw >> 8 & 0xff),
                (byte)(raw & 0xff)});
        }

        /// <summary>
        /// Send (Command = (string)Value) to the serial port
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="values"></param>
        public void sendValue(Command cmd, string value)
        {
            byte[] str = Encoding.ASCII.GetBytes(value);
            int msgLength = str.Length + 3;
            byte[] msg = new byte[msgLength];
            //Send command and value
            msg[0] = (byte)cmd;
            msg[1] = (byte)':';
            for (int count = 0; count < str.Length; count++) msg[count + 2] = str[count];
            msg[msgLength - 1] = 0;
            Write(msg, msgLength);
        }

        /// <summary>
        /// Send a readparam command to the serial port
        /// </summary>
        /// <param name="param"></param>
        public void readParam(Command param)
        {
            byte[] msg = new byte[6];
            //Send command and value
            msg[0] = (byte)Command.PARAMETER_REQUEST;
            msg[1] = (byte)':';
            msg[2] = (byte)param;
            Write(msg, 6);
        }
        public void readParam(object sender, robotSettings.ParameterEventArgs e)
        {
            readParam(e.command);
        }

        /// <summary>
        /// Send a eeprom_direct read command to the serial port
        /// </summary>
        /// <param name="address"></param>
        public void readEe(int address)
        {
            byte[] msg = new byte[6];
            //Send command and value
            msg[0] = (byte)Command.PARAMETER_REQUEST;
            msg[1] = (byte)':';
            msg[2] = (byte)Command.EEPROM_DIRECT;
            msg[3] = (byte)(address >> 8 & 0xff);
            msg[4] = (byte)(address & 0xff);
            Write(msg, 6);
        }

        /// <summary>
        /// Send a eeprom_direct write command to the serial port
        /// </summary>
        /// <param name="address"></param>
        /// <param name="data"></param>
        public void writeEe(int address, byte data)
        {
            byte[] msg = new byte[6];
            //Send command and value
            msg[0] = (byte)Command.EEPROM_DIRECT;
            msg[1] = (byte)':';
            msg[2] = (byte)(address >> 8 & 0xff);
            msg[3] = (byte)(address & 0xff);
            msg[4] = (byte)data;
            msg[5] = (byte)0;
            Write(msg, 6);
        }


    }
    //Outgoing new message event
    public class PortErrorEventArgs : EventArgs
    {
        public string Message;
        public PortErrorEventArgs(string Message)
        {
            this.Message = Message;
        }
    }
}
